package com.modifiertech.lineage.event;

import com.modifiertech.lineage.BaseConst;
import com.modifiertech.lineage.Lineage;
import com.modifiertech.lineage.api.PlayerData;
import com.modifiertech.lineage.commands.CmdHelper;
import com.modifiertech.lineage.handler.ClientSelectedRangeHandler;
import com.modifiertech.lineage.handler.PlayerLevelUpHandler;
import com.modifiertech.lineage.modifier.HealthModifier;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.*;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.ItemFishedEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PlayerDataHandler {
    private static ConcurrentHashMap<String, PlayerData> playerData = new ConcurrentHashMap<>();
    /**获取玩家LineageNBT
     *
     * @param player
     * @return
     */
    public static NBTTagCompound getDataCompoundForPlayer(EntityPlayer player) {
        NBTTagCompound forgeData = player.getEntityData();
        if (!forgeData.hasKey(EntityPlayer.PERSISTED_NBT_TAG)) {
            forgeData.setTag(EntityPlayer.PERSISTED_NBT_TAG, new NBTTagCompound());
        }
        NBTTagCompound persistentData = forgeData.getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG);
        if (!persistentData.hasKey(BaseConst.PLAYER_ROOT_TAG)) {
            persistentData.setTag(BaseConst.PLAYER_ROOT_TAG, new NBTTagCompound());
        }
        return persistentData.getCompoundTag(BaseConst.PLAYER_ROOT_TAG);
    }

    public static synchronized PlayerData get(EntityPlayer player) {
        if (player == null) {
            return null;
        }
        int key = getKey(player);
        String prefix = player instanceof EntityPlayerMP?"mp_":player instanceof EntityPlayerSP?"sp_":"uk_";
        String fullKey = prefix+key;

        PlayerData data = playerData.get(fullKey);

        if (data == null) {
            data = new PlayerData(player);
            playerData.put(fullKey, data);
        }

        // 如果玩家引用不一致，重新加载数据
        if (data.playerWR.get() != player) {
            NBTTagCompound cmp = new NBTTagCompound();
            data.saveToNBT(cmp);
            data = new PlayerData(player);
            playerData.put(fullKey, data);
            data.loadFromNBT(cmp);
        }

        return data;
    }





    private static int getKey(EntityPlayer player) {
        return player == null ? 0 : player.getUniqueID().hashCode();
    }

    public static void cleanup() {
        List<String> removals = new ArrayList<>();
        for (Map.Entry<String, PlayerData> item : playerData.entrySet()) {
            PlayerData d = item.getValue();
            if (d != null && d.playerWR.get() == null) {
                removals.add(item.getKey());
            }
        }
        removals.forEach(i -> playerData.remove(i));
    }
    public static class EventHandler {
        @SubscribeEvent
        public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
            PlayerData data = PlayerDataHandler.get(event.player);
            if (data != null) {
                data.sync();
                HealthModifier.sync(event.player, data);
            }
        }

        @SubscribeEvent
        public static void onPlayerLogOut(PlayerEvent.PlayerLoggedOutEvent event) {
            PlayerData data = PlayerDataHandler.get(event.player);
            if (data != null) {
                data.saveAndSync();
            }
        }

        @SubscribeEvent
        public static void onItemPickup(EntityItemPickupEvent event) {
        }


        @SubscribeEvent
        public static void onBlockDrops(BlockEvent.HarvestDropsEvent event) {
            PlayerData data = PlayerDataHandler.get(event.getHarvester());
            if (data != null) {
                data.blockDrops(event);
            }
        }

        @SubscribeEvent
        public static void onGetBreakSpeed(net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed event) {
            PlayerData data = PlayerDataHandler.get(event.getEntityPlayer());
            if (data != null) {
                data.breakSpeed(event);
            }
        }

        @SubscribeEvent
        public static void onMobDrops(LivingDropsEvent event) {
            if (event.getSource().getTrueSource() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getSource().getTrueSource());
                if (data != null) {
                    data.mobDrops(event);
                }
            }
        }

        @SubscribeEvent
        public static void onHurt(LivingHurtEvent event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) {
                    data.hurt(event);
                }
            }
            if (event.getSource().getTrueSource() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getSource().getTrueSource());
                if (data != null) {
                    data.attackMob(event);
                }
            }
        }

        @SubscribeEvent
        public static void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) {
            PlayerData data = PlayerDataHandler.get(event.getEntityPlayer());
            if (data != null) {
                data.rightClickBlock(event);
            }
        }


        @SubscribeEvent
        public static void onRightClickItem(PlayerInteractEvent.EntityInteract event) {
            PlayerData data = PlayerDataHandler.get(event.getEntityPlayer());
            if (data != null) {
                data.rightClickItem(event);
            }
        }

        @SubscribeEvent
        public static void onFinish(LivingEntityUseItemEvent.Finish event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) {
                    data.itemUseFinish(event);
                }
            }
        }

        @SubscribeEvent
        public static void onEnderTeleport(EnderTeleportEvent event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) {
                    data.enderTeleport(event);
                }
            }
        }

        /**
         * 生物死亡事件,若伤害源为玩家,触发经验获取事件
         *
         * @param event
         */
        @SubscribeEvent
        public static void onMobDeath(LivingDeathEvent event) {
            if (event.getSource().getTrueSource() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getSource().getTrueSource());
                PlayerLevelUpHandler
                        .INSTANCE.gainExp((EntityPlayer) event.getSource().getTrueSource(),
                        PlayerLevelUpHandler.INSTANCE.calculateExpGainWhenKillingMobs((EntityPlayer) event.getSource().getTrueSource()
                                , event.getEntityLiving()));

                if (data != null) {
                    data.killMob(event);
                }
            }
        }

        /**
         * 方块破坏事件
         *
         * @param event
         */
        @SubscribeEvent
        public static void onBlockBreak(BlockEvent.BreakEvent event) {
            EntityPlayer player = event.getPlayer();
            IBlockState state = event.getState();
            Block block = state.getBlock();

            // 检查方块是否会掉落经验
            if (block.getExpDrop(state, event.getWorld(), event.getPos(), event.getExpToDrop()) > 0) {
                PlayerLevelUpHandler
                        .INSTANCE.gainExp(player,
                        PlayerLevelUpHandler.INSTANCE.calculateExpGainWhenBreakBlock(event));
            }
        }

        /**
         * 钓鱼获取物品事件
         *
         * @param event
         */
        @SubscribeEvent
        public static void onPlayerFish(ItemFishedEvent event) {
            PlayerLevelUpHandler
                    .INSTANCE.gainExp(event.getEntityPlayer(),
                    PlayerLevelUpHandler.INSTANCE.calculateExpGainWhenFished(event));
        }


        @SubscribeEvent(receiveCanceled = false, priority = EventPriority.HIGH)
        public static void onPlayerDeath(LivingDeathEvent event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) {
                    data.playerDead(event);
                }
            }
        }

        @SubscribeEvent(receiveCanceled = false, priority = EventPriority.HIGH)
        public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
            PlayerData data = PlayerDataHandler.get((EntityPlayer) event.player);
            if (data != null) {
                HealthModifier.syncWithFullHealth(event.player, data);
                data.playerRespawn(event);
            }
        }

        @SubscribeEvent(receiveCanceled = false, priority = EventPriority.HIGH)
        public static void onPlayerClone(net.minecraftforge.event.entity.player.PlayerEvent.Clone event) {
            PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntityPlayer());
            if (data != null) {
                HealthModifier.sync(event.getEntityPlayer(), data);
                data.playerClone(event);
            }
        }

        @SubscribeEvent(receiveCanceled = false, priority = EventPriority.HIGH)
        public static void onTickPlayer(TickEvent.PlayerTickEvent event) {
            PlayerData data = PlayerDataHandler.get((EntityPlayer) event.player);
            if (data != null) {
                data.tickPlayer(event);
            }
        }

        @SubscribeEvent(receiveCanceled = false, priority = EventPriority.NORMAL)
        public static void onTickPlayerManaHanding(TickEvent.PlayerTickEvent event) {
            Lineage.proxy.handleOnTickPlayerManaHanding(event);
        }

        @SubscribeEvent(receiveCanceled = false, priority = EventPriority.LOW)
        public static void onTickPlayerSelectedRangeClear(TickEvent.PlayerTickEvent event) {
            Lineage.proxy.handleOnTickPlayerSelectedRangeClear(event);
        }


        @SubscribeEvent(receiveCanceled = true, priority = EventPriority.HIGH)
        public static void onLivingFall(LivingFallEvent event) {
            if (event.getEntityLiving() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntityLiving());
                if (data != null) data.livingFallEvent(event);
                ;
            }
        }

        @SubscribeEvent
        public static void onArrowLoose(EntityJoinWorldEvent event) {
            if (event.getEntity() instanceof EntityArrow) {
                EntityArrow arrow = (EntityArrow) event.getEntity();
                if (arrow.shootingEntity instanceof EntityPlayer) {
                    EntityPlayer player = (EntityPlayer) arrow.shootingEntity;
                    PlayerData data = PlayerDataHandler.get(player);
                    if (data != null) data.ArrowLoose(event);
                }
            }
        }

        @SubscribeEvent
        public static void onBowUse(LivingEntityUseItemEvent event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) data.bowUse(event);
            }
        }

        @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = false)
        public static void onPlayerChangeDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
            if (event.player instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get(event.player);
                if (data != null) {
                    HealthModifier.sync(event.player, data);
                    data.playerChangeDimension(event);
                }
            }
        }

        @SubscribeEvent
        public static void onHeal(LivingHealEvent event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) data.heal(event);
                ;
            }
        }

        @SubscribeEvent
        public static void onPlayerExpIncrease(PlayerExpModifyEvent.Post post) {
            CmdHelper.execServerSay(new TextComponentTranslation("lineage.expup.normal.msg", post.getEntityPlayer().getName(), post.getLeftExp()));
        }

        @SubscribeEvent
        public static void onPlayerLevelUp(PlayerLevelUpEvent.Post post) {
            CmdHelper.execServerSay(new TextComponentTranslation("lineage.levelup.msg", post.getEntityPlayer().getName(), post.getOldLevel(), post.getNewLevel()));
        }
        //监听技能遗忘事件
        @SubscribeEvent
        public static void onPlayerSkillForgetAll(SkillLevelForgetAllEvent.Post post){
            PlayerData data = PlayerDataHandler.get(post.getEntityPlayer());
            if (data != null) {
                HealthModifier.sync(post.getEntityPlayer(), data);
            }
            CmdHelper.execServerSay(new TextComponentTranslation("lineage.skill.forgot-all.msg", post.getEntityPlayer().getName()));
        }


        @SubscribeEvent
        public static void onPlayerSkillLevelUp(SkillLevelUpEvent.Post event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) data.skillLevelUp(event);
                data.saveAndSync();
            }
        }

        @SubscribeEvent
        public static void onPlayerSkillShortCutBind(SkillShortCutBindEvent.Post event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) data.skillShortCutBind(event);
                ;
            }
        }

        @SubscribeEvent
        public static void onPlayerSkillAnimateCall(SkillAnimateCallEvent.Post event) {
            if (event.getEntity() instanceof EntityPlayer) {
                PlayerData data = PlayerDataHandler.get((EntityPlayer) event.getEntity());
                if (data != null) data.skillAnimateCall(event);
            }
        }

        @SubscribeEvent
        /**事件处理：技能丢出（方块目标对象）
         * 事件类型：Forge双端事件,实际按客户端处理
         * 备注：onPlayerSkillHandingOff 前置事件处理
         */
        public static void onPlayerSkillHandingOffAimBlock(PlayerInteractEvent.RightClickBlock event) {
            Lineage.proxy.handleOnPlayerSkillHandingOffAimBlock(event);
        }

        @SubscribeEvent
        // todo 理论上该方法如果定义无瞄准目标时不触发任何效果.
        // todo 完全没必要,但考虑到个别情况下,客户端需要做一些渲染,保留该部分的技能事件订阅.
        /**事件处理：技能丢出（无目标对象）
         * 事件类型：Forge双端事件,实际按客户端处理
         * 备注：onPlayerSkillHandingOff 前置事件处理
         *
         */
        public static void onPlayerSkillHandingOffAimEmpty(PlayerInteractEvent.RightClickEmpty event) {
            Lineage.proxy.handleOnPlayerSkillHandingOffAimEmpty(event);
        }

        @SubscribeEvent
        /**事件处理：技能丢出（服务端）
         * 事件类型：Forge双端事件,实际由消息触发,服务端处理
         */
        public static void onPlayerSkillHandingOff(SkillHandingOffEvent.Post event) {
            PlayerData data = PlayerDataHandler.get(event.getEntityPlayer());
            String skillName = data.getHoldingSkillName();
            if (data != null && null != skillName) {
                data.onPlayerSkillHandingOff(skillName, event.getEntityPlayer());
            }
        }
    }

}




